#include "common.h"
#include "index_handle.h"

static int debug2 = 1;

namespace zsyuung_fs
{
    namespace largefile
    {
        
        IndexHandle::IndexHandle(const std::string& base_path, const uint32_t main_block_id)
        {
            std::stringstream tmp_stream;
            tmp_stream << base_path << INDEX_DIR_PREFIX << main_block_id;

            std::string index_path;
            tmp_stream >> index_path;

            file_op_ = new MMapFileOperation(index_path, O_CREAT | O_RDWR | O_LARGEFILE);
            is_load_ = false;
        }

        IndexHandle::~IndexHandle()
        {
            if(file_op_)
            {
                delete file_op_;
            }

            file_op_ = NULL;
            is_load_ = false;
        }
        
        int IndexHandle::create(const int32_t logic_block_id, const int32_t bucket_size, const MMapOption map_option)
        {
            int ret = 0;

            if(debug2)
            {
                printf("create index, block id:%u, bucket size:%d, max_mmap_size:%d, per_mmap_size:%d, first_map_size:%d\n", logic_block_id, bucket_size, map_option.max_mmap_size_, map_option.per_mmap_size_, map_option.first_mmap_size_);
            }
            
            if(is_load_)
            {
                return EXIT_INDEX_ALREADY_LOADED_ERROR; 
            }

            int64_t file_size = file_op_->get_file_size();
            if(file_size < 0)
            {
                return TFS_ERROR;
            }
            else if(file_size == 0)
            {
                IndexHeader i_header;
                i_header.block_info_.block_id_ = logic_block_id;
                i_header.block_info_.seq_no_ = 1;
                i_header.bucket_size_ = bucket_size;

                i_header.index_file_size_ = sizeof(IndexHeader) + bucket_size * sizeof(int32_t);
                
                char* init_data = new char[i_header.index_file_size_];
                memcpy(init_data, &i_header, sizeof(IndexHeader));
                memset(init_data + sizeof(IndexHeader), 0, i_header.index_file_size_ - sizeof(IndexHeader));

                ret = file_op_->pwrite_file(init_data, i_header.index_file_size_, 0);

                delete[] init_data;
                init_data = NULL;

                if(ret != TFS_SUCCESS)
                {
                    return ret;
                }
                
                file_op_->flush_file();
                
                if(ret != TFS_SUCCESS)
                {
                    return ret;
                }
            }
            else
            {
                return EXIT_META_UNEXPECT_FOUND_ERROR;
            }

            ret = file_op_->mmap_file(map_option);
            if(ret != TFS_SUCCESS)
            {
                return ret;
            }
            
            is_load_ = true;
            
            if(debug2)
            {       
                printf("init block success\n");
            }

            return TFS_SUCCESS;
        }

         int IndexHandle::load(const int32_t logic_block_id, const int32_t bucket_size, const MMapOption map_option)
         {
            int ret = TFS_SUCCESS;

            if(is_load_)
            {
                return EXIT_INDEX_ALREADY_LOADED_ERROR; 
            }
            
            int64_t file_size = file_op_->get_file_size();
            if(file_size < 0)
            {
                return file_size;
            }
            else if(file_size == 0)
            {
                return EXIT_INDEX_CORRUPT_ERROR;
            }
            
            MMapOption tmp_map_option = map_option;

            if(file_size > tmp_map_option.first_mmap_size_ && file_size <= tmp_map_option.max_mmap_size_)
            {
                tmp_map_option.first_mmap_size_ = file_size;
            }
            
            ret = file_op_->mmap_file(tmp_map_option);

            if(ret != TFS_SUCCESS)
            {
                return ret;
            }

            if(0 == get_bucket_size() || 0 == block_info()->block_id_)
            {
                fprintf(stderr, "index corrupt error\n");
                return EXIT_INDEX_CORRUPT_ERROR;
            }

            int32_t index_file_size = sizeof(IndexHeader) + get_bucket_size() * sizeof(int32_t);
            if(index_file_size > file_size)
            {
                fprintf(stderr, "index corrupt error\n");
                return EXIT_INDEX_CORRUPT_ERROR;
            }

            if(logic_block_id != block_info()->block_id_)
            {
                fprintf(stderr, "index block id size error\n");
                return EXIT_BLOCKID_CONFLICT_ERROR;
            }
            
            if(bucket_size != get_bucket_size())
            {
                fprintf(stderr, "index bucket size error\n");
                return EXIT_BUCKET_CONFIGURE_ERROR;
            }

            is_load_ = true;

            return TFS_SUCCESS;
         }

        int IndexHandle::remove(const uint32_t logic_block_id)
        {
            if(is_load_)
            {
                if(logic_block_id != block_info()->block_id_)
                {
                    fprintf(stderr, "block id conflict block id:%d, file id:%d\n", logic_block_id, block_info()->block_id_);
                    return EXIT_BLOCKID_CONFLICT_ERROR;
                }
            }

            int ret = file_op_->munmap_file();
            if(ret != TFS_SUCCESS)
            {
                return ret;
            }

            ret = file_op_->unlink_file();
            return ret;
        }
        
        int IndexHandle::flush()
        {
            int ret = file_op_->flush_file();
            
            if(ret != TFS_SUCCESS)
            {
                fprintf(stderr, "index flush fail, desc:%s\n", strerror(errno));
            }
            
            return ret;
        }

        int32_t IndexHandle::write_segment_meta(const int64_t key, MetaInfo& meta)
        {
            int32_t current_offset = 0, previous_offset;

            int ret = hash_find(key, current_offset, previous_offset);
            if(ret == TFS_SUCCESS)
            {
                return EXIT_META_UNEXPECT_FOUND_ERROR;
            }
            else if(ret != EXIT_META_NOT_FOUND_ERROR)
            {
                return ret;
            }
            
            ret = hash_insert(key, previous_offset, meta);

            return ret;
        }

        int32_t IndexHandle::hash_insert(const uint64_t key, int32_t previous_offset, MetaInfo& meta)
        {
            int ret = TFS_SUCCESS;
            MetaInfo tmp_meta_info;
            int32_t current_offset = 0; 

            int32_t slot = static_cast<int32_t>(key) % get_bucket_size();
            
            if(free_head_offset() != 0)
            {
                ret = file_op_->pread_file(reinterpret_cast<char*>(&tmp_meta_info), sizeof(MetaInfo), free_head_offset());
                if(ret != TFS_SUCCESS)
                {
                    return ret;
                }

                current_offset = index_header()->free_head_offset_;
                index_header()->free_head_offset_ = tmp_meta_info.get_next_meta_offset();
            }
            else
            {
                current_offset = index_header()->index_file_size_;
                index_header()->index_file_size_ += sizeof(MetaInfo);
            }

            meta.set_next_meta_offset(0);

            ret = file_op_->pwrite_file(reinterpret_cast<const char*>(&meta), sizeof(MetaInfo), current_offset);
            if(TFS_SUCCESS != ret)
            {
                index_header()->index_file_size_ -= sizeof(MetaInfo);
                return ret;
            }

            if(previous_offset != 0)
            {
                ret = file_op_->pread_file(reinterpret_cast<char*>(&tmp_meta_info), sizeof(MetaInfo), previous_offset);
                if(TFS_SUCCESS != ret)
                {
                    index_header()->index_file_size_ -= sizeof(MetaInfo);
                    return ret;
                }

                tmp_meta_info.set_next_meta_offset(current_offset);
                ret = file_op_->pwrite_file(reinterpret_cast<const char*>(&meta), sizeof(MetaInfo), previous_offset);
                if(TFS_SUCCESS != ret)
                {
                    index_header()->index_file_size_ -= sizeof(MetaInfo);
                    return ret;
                }
            }
            else
            {
                bucket_slot()[slot] = current_offset;
            }

            return TFS_SUCCESS;
        }

        int32_t IndexHandle::hash_find(const uint64_t key, int32_t& current_offset, int32_t& previous_offset)
        {
            int ret = TFS_SUCCESS;
            MetaInfo meta_info;
            current_offset = 0;
            previous_offset = 0;

            int32_t slot = static_cast<int32_t>(key) % get_bucket_size(); 
            
            int32_t pos = bucket_slot()[slot]; 
            
            for(; pos != 0;) 
            {
                ret = file_op_->pread_file(reinterpret_cast<char*>(&meta_info), sizeof(MetaInfo), pos);
                if(ret != TFS_SUCCESS)
                {
                    return ret;
                }

                if(hash_compare(key, meta_info.get_key()))
                {
                    current_offset = pos;
                    return TFS_SUCCESS;
                }

                previous_offset = pos;
                pos = meta_info.get_next_meta_offset();
            }           
            
            return EXIT_META_NOT_FOUND_ERROR; 
        }

        int IndexHandle::update_block_info(const OperType oper_type, const uint32_t modify_size)
        {
            if(block_info()->block_id_ == 0)
            {
                return EXIT_BLOCKID_ZERO_ERROR;
            }

            if(oper_type == C_OPER_INSERT)
            {
                ++block_info()->version_;
                ++block_info()->file_count_;
                ++block_info()->seq_no_;
                block_info()->size_t_ += modify_size;
            }
            else if(oper_type == C_OPER_DELETE)
            {
                ++block_info()->version_;
                --block_info()->file_count_;
                block_info()->size_t_ -= modify_size;
                ++block_info()->del_file_count_;
                block_info()->del_file_count_ += modify_size;
            }
            
            if(debug2)
            {       
                printf("update block info success\n");
            }

            return TFS_SUCCESS;
        }

        int32_t IndexHandle::read_segment_meta(const uint64_t key, MetaInfo& meta)
        {
            int32_t current_offset = 0, previous_offset = 0;

            int32_t ret = hash_find(key, current_offset, previous_offset);
            if(ret == TFS_SUCCESS)
            {
                ret = file_op_->pread_file(reinterpret_cast<char*>(&meta), sizeof(MetaInfo), current_offset);
                return ret;
            }
            else
            {
                return ret;
            }
        }

        int32_t IndexHandle::delete_segment_meta(const uint64_t key)
        {
            int32_t current_offset = 0, previous_offset = 0;

            int32_t ret = hash_find(key, current_offset, previous_offset);
            if(ret != TFS_SUCCESS)
            {
                return ret;
            }
            
            MetaInfo meta_info;
            
            ret = file_op_->pread_file(reinterpret_cast<char*>(&meta_info), sizeof(MetaInfo), current_offset);
            if(TFS_SUCCESS != ret)
            {
                return ret;
            }

            int32_t next_pos = meta_info.get_next_meta_offset();

            if(previous_offset == 0)
            {
                int32_t slot = static_cast<uint32_t>(key) % get_bucket_size();
                bucket_slot()[slot] = next_pos;
            }
            else
            {
                MetaInfo pre_meta_info;
                ret = file_op_->pread_file(reinterpret_cast<char*>(&pre_meta_info), sizeof(MetaInfo), previous_offset);
                if(TFS_SUCCESS != ret)
                {
                    return ret;
                }

                pre_meta_info.set_next_meta_offset(next_pos);

                ret = file_op_->pwrite_file(reinterpret_cast<char*>(&pre_meta_info), sizeof(MetaInfo), previous_offset);
                if(TFS_SUCCESS != ret)
                {
                    return ret;
                }
            }
    
            meta_info.set_next_meta_offset(free_head_offset());
            ret = file_op_->pwrite_file(reinterpret_cast<char*>(&meta_info), sizeof(MetaInfo), current_offset);
            if(TFS_SUCCESS != ret)
            {
                return ret;
            }         

            index_header()->free_head_offset_ = current_offset;

            update_block_info(C_OPER_DELETE, meta_info.get_size());

            return TFS_SUCCESS;
        }

    }
}

